/**
 * Copyright (C) 2009 Noel Ang
 *
 * This file is part of Samsara.
 *
 * Samsara is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Samsara is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Samsara.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.lunarshades.samsara.ui;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JTable;
import javax.swing.table.TableModel;

import org.ahmadsoft.ropes.Rope;
import org.ahmadsoft.ropes.RopeBuilder;

/**
 * KeyListener implementation for tables that will set the table's current
 * selection to the table entry that matches the search term keyed in.
 *
 * @author Noel Ang, sparta.variable@gmail.com
 */
class TableSearchNavigator implements KeyListener {
/**
 * @param column Model column whose values are compared against search terms
 * @param expiry Maximum millisecond duration between additive search updates
 * @param comparator {@link Comparator} implementation for comparing search
 * terms with table entries
 *
 * @throws NullPointerException if model or comparator is null
 * @throws IllegalArgumentException if column &lt; 0 or if expiry &lt; 0
 */
TableSearchNavigator(int column, long expiry, Comparator comparator) {
    mColumn = column;
    mExpiry = expiry;
    mComparator = comparator;
    mLastSearchTime = 0L;
    if (comparator == null) {
        throw new NullPointerException("comparator");
    }
    if (column < 0) {
        throw new IllegalArgumentException("column: " + column);
    }
    if (expiry < 0) {
        throw new IllegalArgumentException("expiry: " + expiry);
    }
}

private boolean isNewSearch() {
    long lastSearchTime = mLastSearchTime;
    assert lastSearchTime >= 0;
    long nowTime = mLastSearchTime = System.currentTimeMillis();
    long delay = nowTime - lastSearchTime;
    return (delay > mExpiry);
}

private Rope searchTerm(KeyEvent e) {
    Rope term = mSearchTerm;

    int keyCode = e.getKeyCode();
    char keyChar = e.getKeyChar();

    if (isNewSearch()) {
        term = mRopeBuilder.build("");
    }
    
    switch (keyCode) {
    case KeyEvent.VK_ESCAPE:
        term = term.delete(0, term.length());
        break;
    case KeyEvent.VK_BACK_SPACE:
        int start = Math.max(0, term.length() - 1);
        term = term.delete(start, term.length());
        break;
    case KeyEvent.VK_UNDEFINED:
        switch (keyChar) {
            case KeyEvent.CHAR_UNDEFINED:
                break;
            default:
                if (!Character.isIdentifierIgnorable((int) keyChar)) {
                    term = term.append(Character.toUpperCase(keyChar));
                }
        }
        break;
    }
    return mSearchTerm = term;
}

private void search(KeyEvent e) {
    JTable table = (JTable) e.getSource();
    TableModel model = table.getModel();
    Rope key = searchTerm(e);
    if (key.length() > 0) {
        for (int i = 0, rows = model.getRowCount(); i < rows; ++i) {
            Object object = model.getValueAt(i, mColumn);
            if (object != null) {
                if (mComparator.match(key, object)) {
                    int viewRow = table.convertRowIndexToView(i);
                    table.changeSelection(viewRow, 0, false, false);
                    break;
                }
            }
        }
    }
}

/**
 * Invoked when a key has been typed. See the class description for {@link
 * KeyEvent} for a definition of a key typed event.
 */
public void keyTyped(KeyEvent e) {
    if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
        search(e);
    }
}

/**
 * Invoked when a key has been pressed. See the class description for {@link
 * KeyEvent} for a definition of a key pressed event.
 */
public void keyPressed(KeyEvent e) {
    int keyCode = e.getKeyCode();
    switch (keyCode) {
    case KeyEvent.VK_ESCAPE:
    case KeyEvent.VK_BACK_SPACE:
        if (e.getSource() instanceof JTable) {
            search(e);
        }
        break;
    }
}

/**
 * Invoked when a key has been released. See the class description for {@link
 * KeyEvent} for a definition of a key released event.
 */
public void keyReleased(KeyEvent e) {
    // NO-OP
}

public static interface Comparator {
    boolean match(Object key, Object value);
}

private final int mColumn;
private final long mExpiry;
private final Comparator mComparator;
private final RopeBuilder mRopeBuilder = new RopeBuilder();
private volatile Rope mSearchTerm = mRopeBuilder.build("");
private volatile long mLastSearchTime;
}
